﻿using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Security.Principal;
using System.ServiceModel.DomainServices.Client;
using System.ServiceModel.DomainServices.Client.ApplicationServices;
using Agility.ViewModels;
using Agility.Web.Resources;
using Agility.Web.Services;
using Moq;
using OneOfAKind.Toolbox.SilverlightTools.Testing;


namespace Agility.Testing.Silverlight.AppDriver
{
    public class AdministrationDriver
    {
        Mock<MockAuthenticationService> mockAuthenticationService;
        Mock<MockDomainClient> mockDomainClient;
        RegistrationFormViewModel registrationFormViewModel;
        InvokeArgs createUserInvokeArgs;
        string errorMessage;
        bool loggedIn;
        bool duplicateUsername;
        bool duplicateEmail;
        bool genericFailure;
        TestType testType;
        
        public AdministrationDriver(TestType testType)
        {
            this.testType = testType;
            if (testType == TestType.UnitTest || testType == TestType.ComponentTest)
            {
                this.mockAuthenticationService = App.Current.Resources["AuthenticationServiceMock"] as Mock<MockAuthenticationService>;
                this.mockDomainClient = new Mock<MockDomainClient>();
            }
            this.createUserInvokeArgs = new InvokeArgs("CreateUser", typeof(CreateUserStatus), null, true);
        }

        ~AdministrationDriver()
        {
            this.registrationFormViewModel = null;
            this.mockDomainClient = null;
            this.mockAuthenticationService = null;
        }

        public bool RegistrationCompleted { get; private set; }

        public static int Logins { get; set; }

        public void RegisterUser(string userName)
        {
            this.RegisterUserAsync(userName, "Test##100", "my friendlyName", "tester@testing.com", "What?", "Exactly", false, false, false);
        }

        public void RegisterUser(string userName, bool duplicateEmail, bool duplicateUsername, bool genericFailure)
        {
            this.RegisterUserAsync(userName, "Test##100", "my friendlyName", "tester@testing.com", "What?", "Exactly", duplicateEmail, duplicateUsername, genericFailure);
        }

        public void RegisterUserAsync(string userName, string password, string friendlyName, string email, string question, string answer, bool duplicateEmail, bool duplicateUsername, bool genericFailure)
        {
            this.duplicateEmail = duplicateEmail;
            this.duplicateUsername = duplicateUsername;
            this.genericFailure = genericFailure;

            if (testType == TestType.UnitTest || testType == TestType.ComponentTest)
            {
                this.SetupAuthorizationMock(userName);

                // Arrange the domainClient an authenticationService for Success
                this.mockDomainClient.Setup(dc => dc.Invoke(It.IsAny<InvokeArgs>())).Returns(new InvokeCompletedResult(CreateUserStatus.Success));
                if (duplicateEmail)
                    this.mockDomainClient.Setup(dc => dc.Invoke(It.IsAny<InvokeArgs>())).Returns(new InvokeCompletedResult(CreateUserStatus.DuplicateEmail));
                if (duplicateUsername)
                    this.mockDomainClient.Setup(dc => dc.Invoke(It.IsAny<InvokeArgs>())).Returns(new InvokeCompletedResult(CreateUserStatus.DuplicateUserName));
                if (genericFailure)
                    this.mockDomainClient.Setup(dc => dc.Invoke(It.IsAny<InvokeArgs>())).Returns(new InvokeCompletedResult(CreateUserStatus.Failure));

                // Arrange the viewmodel
                this.registrationFormViewModel = new RegistrationFormViewModel(mockAuthenticationService.Object, mockDomainClient.Object);
            }
            else
                this.registrationFormViewModel = new RegistrationFormViewModel(WebContext.Current.Authentication);
            this.registrationFormViewModel.PasswordAccessor = () => password;
            this.registrationFormViewModel.PasswordConfirmationAccessor = () => password;
            this.registrationFormViewModel.Answer = answer;
            this.registrationFormViewModel.Email = answer;
            this.registrationFormViewModel.FriendlyName = friendlyName;
            this.registrationFormViewModel.Question = question;

            // Act
            this.registrationFormViewModel.RegistrationCompleted += new EventHandler<RegistrationCompletedEventArgs>(registrationFormViewModel_RegistrationCompleted);
            this.registrationFormViewModel.RegisterUserAsync();
        }

        private void SetupAuthorizationMock(string userName)
        {
            // Arrange the principal and mockAuthenticationService for the user to be created and logged in
            var mockIdentity = new Mock<IIdentity>();
            mockIdentity.SetupGet(i => i.AuthenticationType).Returns("Forms");
            mockIdentity.SetupGet(i => i.IsAuthenticated).Returns(true);
            mockIdentity.SetupGet(i => i.Name).Returns(userName);

            var mockPrincipal = new Mock<IPrincipal>();
            mockPrincipal.SetupGet(p => p.Identity).Returns(mockIdentity.Object);
            mockPrincipal.Setup(p => p.IsInRole(It.IsAny<string>())).Returns(true);

            this.mockAuthenticationService.Setup(a => a.LoginMockery(It.IsAny<LoginParameters>(), It.IsAny<object>()))
                                                        .Returns(new LoginResult(mockPrincipal.Object, true))
                                                        .Callback(() => AdministrationDriver.Logins ++);

            var mockAnonymousIdentity = new Mock<IIdentity>();
            mockAnonymousIdentity.SetupGet(i => i.AuthenticationType).Returns("");
            mockAnonymousIdentity.SetupGet(i => i.IsAuthenticated).Returns(false);
            mockAnonymousIdentity.SetupGet(i => i.Name).Returns("AnonymousUser");
            var mockAnonymousPrinciple = new Mock<IPrincipal>();
            mockAnonymousPrinciple.SetupGet(p => p.Identity).Returns(mockAnonymousIdentity.Object);
            mockAnonymousPrinciple.Setup(p => p.IsInRole(It.IsAny<string>())).Returns(false);
            this.mockAuthenticationService.Setup(a => a.LogoutMockery()).Returns(new LogoutResult(mockAnonymousPrinciple.Object));
        }

        void registrationFormViewModel_RegistrationCompleted(object sender, RegistrationCompletedEventArgs e)
        {
            this.registrationFormViewModel.RegistrationCompleted -= new EventHandler<RegistrationCompletedEventArgs>(registrationFormViewModel_RegistrationCompleted);
            this.loggedIn = e.LoggedIn;
            this.errorMessage = e.ErrorMessage;
            this.RegistrationCompleted = true;
        }
        
        public RegistrationResult ConfirmRegistrationResult()
        {
            RegistrationResult isExpectedResult = RegistrationResult.UnexpectedError;
            if (this.testType == TestType.UnitTest || this.testType == TestType.ComponentTest)
            {
                this.mockDomainClient.Verify(dc => dc.Invoke(It.IsAny<InvokeArgs>())); 
            }
            if (this.duplicateUsername || this.duplicateEmail || this.genericFailure)
            {
                if (this.testType == TestType.UnitTest || this.testType == TestType.ComponentTest)
                {
                    this.mockAuthenticationService.Verify(auth => auth.LoginMockery(It.IsAny<LoginParameters>(), It.IsAny<object>()), Times.Exactly(AdministrationDriver.Logins)); 
                }
                if (!this.registrationFormViewModel.DialogResult.HasValue)
                    isExpectedResult = RegistrationResult.IsExpectedDialogResult;
                if (this.duplicateEmail)
                    foreach (ValidationResult validationResult in registrationFormViewModel.ValidationErrors)
                    {
                        if (validationResult.ErrorMessage == ValidationErrorResources.CreateUserStatusDuplicateEmail && validationResult.MemberNames.First() == "Email")
                        {
                            isExpectedResult |= RegistrationResult.HasValidationErrors;
                            isExpectedResult |= RegistrationResult.IsExpectedErrorMessage;
                        }
                    }
                else if (this.duplicateUsername)
                    foreach (ValidationResult validationResult in registrationFormViewModel.ValidationErrors)
                    {
                        if (validationResult.ErrorMessage == ValidationErrorResources.CreateUserStatusDuplicateUserName && validationResult.MemberNames.First() == "UserName")
                        {
                            isExpectedResult |= RegistrationResult.HasValidationErrors;
                            isExpectedResult |= RegistrationResult.IsExpectedErrorMessage;
                        }
                    }
                else
                {
                    if (this.registrationFormViewModel.ValidationErrors.Count > 0)
                    {
                        isExpectedResult |= RegistrationResult.HasValidationErrors;
                    }
                    if (this.errorMessage == ValidationErrorResources.CreateUserStatusFailed)
                        isExpectedResult |= RegistrationResult.IsExpectedErrorMessage;
                }
            }
            else
            {
                if (this.testType == TestType.UnitTest || this.testType == TestType.ComponentTest)
                {
                    this.mockAuthenticationService.Verify(auth => auth.LoginMockery(It.IsAny<LoginParameters>(), It.IsAny<object>())); 
                }
                if (this.registrationFormViewModel.HasValidationErrors)
                    isExpectedResult |= RegistrationResult.HasValidationErrors;
                if (this.registrationFormViewModel.DialogResult.HasValue && registrationFormViewModel.DialogResult.Value)
                    isExpectedResult |= RegistrationResult.IsExpectedDialogResult;
            }
            if (this.loggedIn)
                isExpectedResult |= RegistrationResult.LoggedIn;
            return isExpectedResult;
        }

        public void Login(string username)
        {
            this.SetupAuthorizationMock(username);
            this.mockAuthenticationService.Object.Login(username, "Test##100");
        }

        public void Logout()
        {
            this.mockAuthenticationService.Object.Logout(false);
        }
    }
}
